Вы работаете в интернет-магазине «Стримчик», который продаёт по всему миру компьютерные игры. Из открытых источников доступны исторические данные о продажах игр, оценки пользователей и экспертов, жанры и платформы (например, Xbox или PlayStation). Вам нужно выявить определяющие успешность игры закономерности. Это позволит сделать ставку на потенциально популярный продукт и спланировать рекламные кампании.
Перед вами данные до 2016 года. Представим, что сейчас декабрь 2016 г., и вы планируете кампанию на 2017-й. Нужно отработать принцип работы с данными. Неважно, прогнозируете ли вы продажи на 2017 год по данным 2016-го или же 2027-й — по данным 2026 года.
В наборе данных попадается аббревиатура ESRB (Entertainment Software Rating Board) — это ассоциация, определяющая возрастной рейтинг компьютерных игр. ESRB оценивает игровой контент и присваивает ему подходящую возрастную категорию, например, «Для взрослых», «Для детей младшего возраста» или «Для подростков».
Name — название игрыPlatform — платформаYear_of_Release — год выпускаGenre — жанр игрыNA_sales — продажи в Северной Америке (миллионы проданных копий)EU_sales — продажи в Европе (миллионы проданных копий)JP_sales — продажи в Японии (миллионы проданных копий)Other_sales — продажи в других странах (миллионы проданных копий)Critic_Score — оценка критиков (максимум 100)User_Score — оценка пользователей (максимум 10)Rating — рейтинг от организации ESRB (англ. Entertainment Software Rating Board). Эта ассоциация определяет рейтинг компьютерных игр и присваивает им подходящую возрастную категорию.Данные за 2016 год могут быть неполными.
Открыть файл с данными и изучить общую информацию.
# импорт библиотек
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import plotly.express as px
import plotly.graph_objects as go
import plotly.io as pio
from scipy import stats as st
# белая тема plotly
pio.templates.default = 'plotly_white'
# увеличение числа видимых столбцов в таблице
pd.set_option("display.max_columns", 100)
# чтение файла данных в датафрейм из папки по умолчанию и из рабочей директории
try:
df = pd.read_csv('/datasets/games.csv', sep=',')
except:
df = pd.read_csv('games.csv', sep=',')
# получение первых 10 строк таблицы df
df.head(10)
| Name | Platform | Year_of_Release | Genre | NA_sales | EU_sales | JP_sales | Other_sales | Critic_Score | User_Score | Rating | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | Wii Sports | Wii | 2006.0 | Sports | 41.36 | 28.96 | 3.77 | 8.45 | 76.0 | 8 | E |
| 1 | Super Mario Bros. | NES | 1985.0 | Platform | 29.08 | 3.58 | 6.81 | 0.77 | NaN | NaN | NaN |
| 2 | Mario Kart Wii | Wii | 2008.0 | Racing | 15.68 | 12.76 | 3.79 | 3.29 | 82.0 | 8.3 | E |
| 3 | Wii Sports Resort | Wii | 2009.0 | Sports | 15.61 | 10.93 | 3.28 | 2.95 | 80.0 | 8 | E |
| 4 | Pokemon Red/Pokemon Blue | GB | 1996.0 | Role-Playing | 11.27 | 8.89 | 10.22 | 1.00 | NaN | NaN | NaN |
| 5 | Tetris | GB | 1989.0 | Puzzle | 23.20 | 2.26 | 4.22 | 0.58 | NaN | NaN | NaN |
| 6 | New Super Mario Bros. | DS | 2006.0 | Platform | 11.28 | 9.14 | 6.50 | 2.88 | 89.0 | 8.5 | E |
| 7 | Wii Play | Wii | 2006.0 | Misc | 13.96 | 9.18 | 2.93 | 2.84 | 58.0 | 6.6 | E |
| 8 | New Super Mario Bros. Wii | Wii | 2009.0 | Platform | 14.44 | 6.94 | 4.70 | 2.24 | 87.0 | 8.4 | E |
| 9 | Duck Hunt | NES | 1984.0 | Shooter | 26.93 | 0.63 | 0.28 | 0.47 | NaN | NaN | NaN |
# получение общей информации о данных в таблице df
df.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 16715 entries, 0 to 16714 Data columns (total 11 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 Name 16713 non-null object 1 Platform 16715 non-null object 2 Year_of_Release 16446 non-null float64 3 Genre 16713 non-null object 4 NA_sales 16715 non-null float64 5 EU_sales 16715 non-null float64 6 JP_sales 16715 non-null float64 7 Other_sales 16715 non-null float64 8 Critic_Score 8137 non-null float64 9 User_Score 10014 non-null object 10 Rating 9949 non-null object dtypes: float64(6), object(5) memory usage: 1.4+ MB
В каждой строке таблицы — данные об конкретной компьютерной игре. Часть колонок описывает саму игру: название, игровая платформа, жанр, год релиза. Другая сообщает о продажах в различных в различных регионах мира. Наконец, последняя указывает рейтинги игры в профессиональном сообществе и среди игроков.
Предварительно можно утверждать, что данных достаточно для проверки гипотез. Но встречаются пропуски в данных, а в названиях колонок — расхождения с хорошим стилем. Часть столбцов требует смены типа данных.
Чтобы двигаться дальше, нужно устранить проблемы в данных, в частности, проверить их на выбросы, аномалии, наличие явных дубликатов.
Подготовить данные:
# приведение к нижнему регистру названий столбцов
df.set_axis(df.columns.str.lower(), axis='columns', copy=False)
df.columns
Index(['name', 'platform', 'year_of_release', 'genre', 'na_sales', 'eu_sales',
'jp_sales', 'other_sales', 'critic_score', 'user_score', 'rating',
'total_sales'],
dtype='object')
В смене типов данных нуждаются два столбца year_of_release и user_score. Год релиза игры был воспринят как число. Посмотрим, почему то же самое не произошло с оценкой пользователей.
# поиск строковых значений в столбце user_score
score_uniques = df.user_score.unique()
non_float = []
for item in score_uniques:
try:
float(item)
except:
non_float.append(item)
non_float
['tbd']
В столбце user_score (оценка пользователей) встречается значение tbd. Аббревиатура «tbd»" расшифровывается как «To Be Determined», то есть «Будет определено». Используется когда разработчик той или иной игры уже сообщил о том, что она готовится к выходу, но пока еще не может назвать точную дату. Также используется при выходе новых аддонов. Так или иначе значение tbd означает, что пользовательская оценка для игры не определена.
Применяемые преобразования типов:
year_of_release в формат даты отложим до момента, пока не будут обработаны пропуски.user_score меняем тип на числовой, при этом значение tbd преобразуется в NaN.# преобразование типа в столбце year_of_release с заменой значений tbd на NaN
df['user_score'] = pd.to_numeric(df['user_score'], errors='coerce')
# количество отсутствующих значений в столбцах
df.isnull().sum()
name 2 platform 0 year_of_release 269 genre 2 na_sales 0 eu_sales 0 jp_sales 0 other_sales 0 critic_score 8578 user_score 9125 rating 6766 dtype: int64
Наибольшее количество пропусков находится в столбцах с рейтингами. Отсутствие значений в столбцах critic_score и user_scoreмало о чем говорит. Посмотрим на уникальные значения в столбце rating.
# просмотр уникальных значений столбца rating
df.rating.unique()
array(['E', nan, 'M', 'T', 'E10+', 'K-A', 'AO', 'EC', 'RP'], dtype=object)
Расшифровка категорий рейтинга ESRB:
Среди значений столбца rating встречается категория RP. Ее можно воспринимать как отсутствие рейтинга и именно ею и заменить отсутствующие значения.
Применяемые преобразования:
NaN в столбцах name и year_of_release, так как их немного.year_of_release меняем тип на целочисленный, так располагаем не датой. а только годом релиза.user_scor меняем тип на числовой, при этом значение tbd преобразуется в NaN.rating значение NaN меняем на RP - категория с неизвестным рейтингом.# удаление пропусков
df.dropna(subset=['name', 'year_of_release'], inplace=True)
# смена типа в столбце year_of_release
df['year_of_release'] = df['year_of_release'].astype('int64')
# замена пропусков в столбце rating
df['rating'].fillna('RP', inplace=True)
# проверка смены типов данных
df.info()
<class 'pandas.core.frame.DataFrame'> Int64Index: 16444 entries, 0 to 16714 Data columns (total 11 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 name 16444 non-null object 1 platform 16444 non-null object 2 year_of_release 16444 non-null int64 3 genre 16444 non-null object 4 na_sales 16444 non-null float64 5 eu_sales 16444 non-null float64 6 jp_sales 16444 non-null float64 7 other_sales 16444 non-null float64 8 critic_score 7983 non-null float64 9 user_score 7463 non-null float64 10 rating 16444 non-null object dtypes: float64(6), int64(1), object(4) memory usage: 1.5+ MB
# оценка количества отсутствующих значений
df.isnull().sum()
name 0 platform 0 year_of_release 0 genre 0 na_sales 0 eu_sales 0 jp_sales 0 other_sales 0 critic_score 8461 user_score 8981 rating 0 dtype: int64
Проведем проверку на явные дубликаты строк, а также неявные дубликаты в значениях категориальных признаков: name, genre и platform.
# проверка на явные дубликаты в строках
print('Число дубликатов:', df.duplicated().sum())
Число дубликатов: 0
# поиск повторяющихся значений в столбце name
df['name'].value_counts()
Need for Speed: Most Wanted 12
LEGO Marvel Super Heroes 9
Ratatouille 9
FIFA 14 9
Terraria 8
..
Loving Life with Hello Kitty & Friends 1
Scrabble (Others sales) 1
Viva Pinata: Party Animals 1
Sands of Destruction 1
Haitaka no Psychedelica 1
Name: name, Length: 11426, dtype: int64
# проверка уникальности одного из названий
df[df['name'] == 'Need for Speed: Most Wanted']
| name | platform | year_of_release | genre | na_sales | eu_sales | jp_sales | other_sales | critic_score | user_score | rating | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 253 | Need for Speed: Most Wanted | PS2 | 2005 | Racing | 2.03 | 1.79 | 0.08 | 0.47 | 82.0 | 9.1 | T |
| 523 | Need for Speed: Most Wanted | PS3 | 2012 | Racing | 0.71 | 1.46 | 0.06 | 0.58 | NaN | NaN | RP |
| 1190 | Need for Speed: Most Wanted | X360 | 2012 | Racing | 0.62 | 0.78 | 0.01 | 0.15 | 83.0 | 8.5 | T |
| 1591 | Need for Speed: Most Wanted | X360 | 2005 | Racing | 1.00 | 0.13 | 0.02 | 0.10 | 83.0 | 8.5 | T |
| 1998 | Need for Speed: Most Wanted | XB | 2005 | Racing | 0.53 | 0.46 | 0.00 | 0.05 | 83.0 | 8.8 | T |
| 2048 | Need for Speed: Most Wanted | PSV | 2012 | Racing | 0.33 | 0.45 | 0.01 | 0.22 | NaN | NaN | RP |
| 3581 | Need for Speed: Most Wanted | GC | 2005 | Racing | 0.43 | 0.11 | 0.00 | 0.02 | 80.0 | 9.1 | T |
| 5972 | Need for Speed: Most Wanted | PC | 2005 | Racing | 0.02 | 0.23 | 0.00 | 0.04 | 82.0 | 8.5 | T |
| 6273 | Need for Speed: Most Wanted | WiiU | 2013 | Racing | 0.13 | 0.12 | 0.00 | 0.02 | NaN | NaN | RP |
| 6410 | Need for Speed: Most Wanted | DS | 2005 | Racing | 0.24 | 0.01 | 0.00 | 0.02 | 45.0 | 6.1 | E |
| 6473 | Need for Speed: Most Wanted | GBA | 2005 | Racing | 0.19 | 0.07 | 0.00 | 0.00 | NaN | 8.3 | E |
| 11715 | Need for Speed: Most Wanted | PC | 2012 | Racing | 0.00 | 0.06 | 0.00 | 0.02 | 82.0 | 8.5 | T |
Среди названий игр встречаются повторные, но они отличаются по другим характеристикам - игровой платформой или годом выпуска.
Проверим категориальные признаки genre и platform и чуть ближе познакомимся с жанрами и игровыми платформами.
# просмотр уникальных значений столбца genre
df.genre.unique()
array(['Sports', 'Platform', 'Racing', 'Role-Playing', 'Puzzle', 'Misc',
'Shooter', 'Simulation', 'Action', 'Fighting', 'Adventure',
'Strategy'], dtype=object)
# просмотр уникальных значений столбца platform
df.platform.unique()
array(['Wii', 'NES', 'GB', 'DS', 'X360', 'PS3', 'PS2', 'SNES', 'GBA',
'PS4', '3DS', 'N64', 'PS', 'XB', 'PC', '2600', 'PSP', 'XOne',
'WiiU', 'GC', 'GEN', 'DC', 'PSV', 'SAT', 'SCD', 'WS', 'NG', 'TG16',
'3DO', 'GG', 'PCFX'], dtype=object)
Данные охватывают несколько поколений популярных игровых приставок, выпускавшихся с 1980 по 2016 год.
Список производителей и названия игровых приставок:
В данных не дубликатов обнаружилось.
Ранее мы уже просмотрели уникальные значения в категориальных признаках и там аномалии не обнаружились. Изучим числовые признаки. И для начала взглянем на признаки с дискретными значениями - year_of_release, user_score, critic_score.
# признаки с дискретными значениями
df['year_of_release'].unique()
array([2006, 1985, 2008, 2009, 1996, 1989, 1984, 2005, 1999, 2007, 2010,
2013, 2004, 1990, 1988, 2002, 2001, 2011, 1998, 2015, 2012, 2014,
1992, 1997, 1993, 1994, 1982, 2016, 2003, 1986, 2000, 1995, 1991,
1981, 1987, 1980, 1983], dtype=int64)
df['user_score'].unique()
array([8. , nan, 8.3, 8.5, 6.6, 8.4, 8.6, 7.7, 6.3, 7.4, 8.2, 9. , 7.9,
8.1, 8.7, 7.1, 3.4, 5.3, 4.8, 3.2, 8.9, 6.4, 7.8, 7.5, 2.6, 7.2,
9.2, 7. , 7.3, 4.3, 7.6, 5.7, 5. , 9.1, 6.5, 8.8, 6.9, 9.4, 6.8,
6.1, 6.7, 5.4, 4. , 4.9, 4.5, 9.3, 6.2, 4.2, 6. , 3.7, 4.1, 5.8,
5.6, 5.5, 4.4, 4.6, 5.9, 3.9, 3.1, 2.9, 5.2, 3.3, 4.7, 5.1, 3.5,
2.5, 1.9, 3. , 2.7, 2.2, 2. , 9.5, 2.1, 3.6, 2.8, 1.8, 3.8, 0. ,
1.6, 9.6, 2.4, 1.7, 1.1, 0.3, 1.5, 0.7, 1.2, 2.3, 0.5, 1.3, 0.2,
0.6, 1.4, 0.9, 1. , 9.7])
df['critic_score'].unique()
array([76., nan, 82., 80., 89., 58., 87., 91., 61., 97., 95., 77., 88.,
83., 94., 93., 85., 86., 98., 96., 90., 84., 73., 74., 78., 92.,
71., 72., 68., 62., 49., 67., 81., 66., 56., 79., 70., 59., 64.,
75., 60., 63., 69., 50., 25., 42., 44., 55., 48., 57., 29., 47.,
65., 54., 20., 53., 37., 38., 33., 52., 30., 32., 43., 45., 51.,
40., 46., 39., 34., 41., 36., 31., 27., 35., 26., 19., 28., 23.,
24., 21., 17., 13.])
В столбцах year_of_release, user_score, critic_score аномалий не оказалось. Посмотрим данные в столбцах продаж - na_sales, eu_sales, jp_sales, other_sales. Сначала убедимся, что там нет отрицательных значений (это послужит косвенным признаком достоверности данных). Затем удалим строки, где суммы продаж во всех колонках равны нулю. После построим графики boxplot для этих признаков.
# выбор строк, где хотя бы один из признаков отрицательный
print('Число строк с отрицательными продажами:',
df[(df['na_sales'] < 0) | (df['eu_sales'] < 0) | (df['jp_sales'] < 0) | (df['other_sales'] < 0)]['name'].sum()
)
Число строк с отрицательными продажами: 0
# удаление строк с отсутствием продаж
df = df[(df['na_sales'] > 0) | (df['eu_sales'] > 0) | (df['jp_sales'] > 0) | (df['other_sales'] > 0)]
# график оценки выбросов
fig = px.box(df[['na_sales', 'eu_sales', 'jp_sales', 'other_sales']],
title='График оценки выбросов <br>в распределениях продаж до очистки')
fig.update_xaxes(title_text='Регион продаж')
fig.update_yaxes(title_text='Объем продаж, млн. долл.')
fig.show()
# функция обрезки выбросов
def cut_outliers(dt, cols):
for col in cols:
if dt[col].dtypes == 'int64' or dt[col].dtypes == 'float64':
pass# расчет процентилей
quantiles = dt[col].quantile(q=[.001, .99])
dt[col] = dt.loc[(dt[col] >= quantiles.iloc[0]) & (dt[col] <= quantiles.iloc[1]), col].copy()
return
# обрезка выбросов для выбранных признаков
to_cut = ['na_sales', 'eu_sales', 'jp_sales', 'other_sales']
cut_outliers(df, to_cut)
# график проверки выбросов
fig = px.box(df[['na_sales', 'eu_sales', 'jp_sales', 'other_sales']],
title='График оценки выбросов <br>в распределениях продаж после очистки')
fig.update_xaxes(title_text='Регион продаж')
fig.update_yaxes(title_text='Объем продаж, млн. долл.')
fig.show()
# удаление строк с пустыми значениями продаж
df = df[(df['na_sales'].notna()) | (df['eu_sales'].notna()) | (df['jp_sales'].notna()) | (df['other_sales'].notna())]
print('Размер датасета после удаления выбросов:', df.shape)
Размер датасета после удаления выбросов: (16416, 11)
# создание столбца с общим объемом продаж total_sales
df['total_sales'] = df.filter(like='_sales').sum(axis=1)
df.head()
| name | platform | year_of_release | genre | na_sales | eu_sales | jp_sales | other_sales | critic_score | user_score | rating | total_sales | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 5 | Tetris | GB | 1989 | Puzzle | NaN | NaN | NaN | 0.58 | NaN | NaN | RP | 0.58 |
| 9 | Duck Hunt | NES | 1984 | Shooter | NaN | 0.63 | 0.28 | 0.47 | NaN | NaN | RP | 1.38 |
| 14 | Kinect Adventures! | X360 | 2010 | Misc | NaN | NaN | 0.24 | NaN | 61.0 | 6.3 | E | 0.24 |
| 16 | Grand Theft Auto V | PS3 | 2013 | Action | NaN | NaN | 0.98 | NaN | 97.0 | 8.2 | M | 0.98 |
| 17 | Grand Theft Auto: San Andreas | PS2 | 2004 | Action | NaN | 0.40 | 0.41 | NaN | 95.0 | 9.0 | M | 0.81 |
На этапе подготовки данных была проведена замена типов данных для столбцов year_of_release и user_score. Были удалены немногочленные пропуски в столбцах name, platform, year_of_release и обработаны пропуски в столбце rating. Данные были проверены на наличие явных и неявных дубликатов, а также аномалий в данных. Были удалены сильные выбросы в данных об объемах продаж. Добавлен столбец суммарных продаж total_sales.
В датасете отсутствуют данные о рейтингах для половины игр. Если заменить их средними значениями - это внесет искажение при их анализе, а при удалении потеряются данные по продажам. Однако, в данном проекте исследования ведутся по срезам отдельных столбцов, все они одновременно не нужны. Раз так, пока NaN в датасете можно оставить.
Провести исследовательский анализ данных:
# диаграмма выпуска игр в разные годы
fig = px.area(df.groupby('year_of_release')['name'].count(),
title='Годовой объем выпуска игр в разные годы')
fig.update_xaxes(title_text='Год выпуска')
fig.update_yaxes(title_text='Кол-во игр, ед.')
fig.update_layout(showlegend=False)
fig.show()
До 1994 кол-во выпускаемых игр составляло менее 100 шт./год. За первые 10 лет всего было выпущено около 200 игр. Так что период с 1980 до 1995 года не представляет особой важности.
# создание датафрейма с индексом и столбцом platform
platform_games = df.pivot_table(index='platform', values='total_sales', aggfunc=['count'])
platform_games = platform_games['count']['total_sales'].reset_index()
platform_games.set_index(keys=platform_games.platform, inplace=True)
# диаграмма выпуска игр для различных платформ
fig = px.bar(platform_games.sort_values(by='total_sales', ascending=True),
orientation='h',
color='platform',
title='Число выпущенных игр для разных игровых платформ')
fig.update_xaxes(title_text='Кол-во игр, ед.')
fig.update_yaxes(title_text='Игровая платформа')
fig.show()
# топ-5 платформ по продажам
top_sales5 = df.groupby('platform')['total_sales'].count().sort_values(ascending=False).head(5).index
print('Топ-5 платформ по продажам:', list(top_sales5))
Топ-5 платформ по продажам: ['PS2', 'DS', 'PS3', 'Wii', 'X360']
# динамика продаж топ-5 игровых платформ
fig = px.line(df.query('platform in @top_sales5 & year_of_release >= 2000')
.pivot_table(index='year_of_release', columns='platform', values='total_sales', aggfunc='sum', fill_value=0),
title='Динамика продаж топ-5 игровых платформ')
fig.update_xaxes(title_text='Год выпуска')
fig.update_yaxes(title_text='Объем продаж, млн. долл.')
fig.show()
Определение списка популярных прежде платформ:
# топ-10 платформ по продажам
top_sales10 = df.groupby('platform')['total_sales'].count().sort_values(ascending=False).head(10).index
top10_platform_years_sales = (
df.query('platform in @top_sales10')
.pivot_table(index='platform', columns='year_of_release', values='total_sales', aggfunc='sum', fill_value=0)
)
# топ устаревших платформ
old_top = top10_platform_years_sales[top10_platform_years_sales[2016] == 0].index
print('Топ устаревших платформ:', list(old_top))
Топ устаревших платформ: ['DS', 'GBA', 'PS', 'PS2', 'PSP', 'XB']
Определение периода жизни платформ:
# первый и последний годы жизни игровой платформы
platform_years = df.query('total_sales > 0').groupby('platform')['year_of_release'].agg([min, max])
# список устаревших платформ
old_platforms = platform_years[platform_years['max'] < 2016].index
# срок жизни игровых платформ
old_platforms_dur = platform_years['max'][old_platforms] - platform_years['min'][old_platforms] + 1
print('Характерный период жизни платформы:', np.floor(old_platforms_dur.mean()))
Характерный период жизни платформы: 8.0
Для дальнейшего анализа оставим только игровые платформы с ненулевым объемом продаж в 2016 году, а глубину истории для первоначальной оценки ограничим последними 8-ю годами - c 2008 по 2016 годы.
# список актуальных платформ
new_platforms = platform_years[platform_years['max'] == 2016].index
print('Актуальные в 2016-ом году платформы:', list(new_platforms))
Актуальные в 2016-ом году платформы: ['3DS', 'PC', 'PS3', 'PS4', 'PSV', 'Wii', 'WiiU', 'X360', 'XOne']
# актуальный датасет
new_df = df.query('platform in @new_platforms & year_of_release >= 2008').copy()
new_df
| name | platform | year_of_release | genre | na_sales | eu_sales | jp_sales | other_sales | critic_score | user_score | rating | total_sales | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 14 | Kinect Adventures! | X360 | 2010 | Misc | NaN | NaN | 0.24 | NaN | 61.0 | 6.3 | E | 0.24 |
| 16 | Grand Theft Auto V | PS3 | 2013 | Action | NaN | NaN | 0.98 | NaN | 97.0 | 8.2 | M | 0.98 |
| 23 | Grand Theft Auto V | X360 | 2013 | Action | NaN | NaN | 0.06 | NaN | 97.0 | 8.1 | M | 0.06 |
| 29 | Call of Duty: Modern Warfare 3 | X360 | 2011 | Shooter | NaN | NaN | 0.13 | NaN | 88.0 | 3.4 | M | 0.13 |
| 31 | Call of Duty: Black Ops 3 | PS4 | 2015 | Shooter | NaN | NaN | 0.36 | NaN | NaN | NaN | RP | 0.36 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 16705 | 15 Days | PC | 2009 | Adventure | 0.0 | 0.01 | 0.00 | 0.0 | 63.0 | 5.8 | RP | 0.01 |
| 16707 | Aiyoku no Eustia | PSV | 2014 | Misc | 0.0 | 0.00 | 0.01 | 0.0 | NaN | NaN | RP | 0.01 |
| 16710 | Samurai Warriors: Sanada Maru | PS3 | 2016 | Action | 0.0 | 0.00 | 0.01 | 0.0 | NaN | NaN | RP | 0.01 |
| 16712 | Haitaka no Psychedelica | PSV | 2016 | Adventure | 0.0 | 0.00 | 0.01 | 0.0 | NaN | NaN | RP | 0.01 |
| 16714 | Winning Post 8 2016 | PSV | 2016 | Simulation | 0.0 | 0.00 | 0.01 | 0.0 | NaN | NaN | RP | 0.01 |
5623 rows × 12 columns
# динамика продаж существующих игровых платформ
fig = px.line(new_df.pivot_table(index='year_of_release', columns='platform', values='total_sales', aggfunc='sum', fill_value=0),
title='Динамика продаж существующих игровых платформ')
fig.update_xaxes(title_text='Год выпуска')
fig.update_yaxes(title_text='Объем продаж, млн. долл.')
fig.show()
Определим наиболее перспективные платформы на основании роста за последние несколько лет. Если характерный период жизни платформы составляет 8 лет, можно предположить, что в первые 4 года от этого срока популярность платформы растет, во вторые 4 - падает. Убедимся в этом.
Определение периода роста:
MA(3) в течении двух лет.В выборку не будут включены платформы в стадии роста продаж и те, чей срок жизни составил менее 1-2 года.
# таблица суммарных продаж по платформам в разные годы
platform_sales = df.pivot_table(index='platform', columns='year_of_release', values='total_sales', aggfunc='sum').copy()
platform_sales.fillna(0, inplace=True)
# функция проверки разрывов в данных
def start_checking(data, row, col, num):
counter = 0
for i in range(num):
if data.iloc[row, col-i] > 0:
counter += 1
return counter == num
# функция расчета скользящего среднего
def ma(data, row, col, period):
values = [data.iloc[row, col-n] for n in range(period) if col >= (period - 1)]
return sum(values) / period
ma_period = 3
platform_interval = {}
# цикл по индексам пивот-таблицы platform_sales
for i in range(platform_sales.shape[0]):
flag_start = False
start = 0
# счетчик убыточных лет
counter = 0
# итерируемся со сдвигом значения вперед для фиксации отсутствия данных
for j in range(ma_period-1, platform_sales.shape[1]):
# начало продаж
if start_checking(platform_sales, i, j, ma_period) and flag_start == False:
flag_start = True
start = (j - (ma_period-1))
j = 0
# итерируемся со сдвигом значения вперед для сравнения с предыдущим
for j in range(start, platform_sales.shape[1]):
# фиксация спада продаж
if flag_start == True:
if counter < 2:
# сравниваем скользящие средние
if ma(platform_sales, i, j, ma_period) < ma(platform_sales, i, j-1, ma_period):
counter += 1
else:
counter = 0
pass
# запись длины периода
elif counter == 2:
platform_interval[platform_sales.index[i]] = (j - 2) - start
break
else:
pass
# конвертирум словарь с периодами в датафрейм
platform_interval = pd.DataFrame.from_dict(platform_interval, orient='index')
# добавим в датафрейм индекс с названиями платформ
intervals = platform_interval.copy().reset_index()
intervals.set_index(keys=platform_interval.index, inplace=True)
intervals = intervals.set_axis(['platform', 'interval'], axis=1)
# график периодов роста продаж для различных платформ
fig = px.bar(intervals,
orientation='h',
color='platform',
title='Зафиксированный период роста продаж игр <br>для различных платформ')
fig.update_xaxes(title_text='Кол-во лет, ед.')
fig.update_yaxes(title_text='Игровая платформа')
fig.show()
print(f'Средний период роста продаж составляет: {round(intervals["interval"].mean(), 2)} года')
Средний период роста продаж составляет: 4.64 года
Наше первоначальное о предположение о том, что полный период роста продаж для платформ составляет 4 года подтвердилось. Можно также выделить период вероятного продолжения роста после его начала. Очевидно, что для фиксации тенденции к росту нужно минимум два годовых значения. Таким образом, период актуальности платформы окажется равным 2 годам.
Дальнейший отбор платформ будем делать исходя из следующих критериев:
# коррекция датасета
new_df = new_df.query('platform in @new_platforms & year_of_release >= 2015')
new_df
| name | platform | year_of_release | genre | na_sales | eu_sales | jp_sales | other_sales | critic_score | user_score | rating | total_sales | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 31 | Call of Duty: Black Ops 3 | PS4 | 2015 | Shooter | NaN | NaN | 0.36 | NaN | NaN | NaN | RP | 0.36 |
| 77 | FIFA 16 | PS4 | 2015 | Sports | 1.12 | NaN | 0.06 | NaN | 82.0 | 4.3 | E | 1.18 |
| 87 | Star Wars Battlefront (2015) | PS4 | 2015 | Shooter | NaN | NaN | 0.22 | NaN | NaN | NaN | RP | 0.22 |
| 94 | FIFA 17 | PS4 | 2016 | Sports | 0.66 | NaN | 0.08 | NaN | 85.0 | 5.0 | E | 0.74 |
| 99 | Call of Duty: Black Ops 3 | XOne | 2015 | Shooter | NaN | NaN | 0.01 | NaN | NaN | NaN | RP | 0.01 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 16699 | The Longest 5 Minutes | PSV | 2016 | Action | 0.00 | 0.0 | 0.01 | 0.0 | NaN | NaN | RP | 0.01 |
| 16703 | Strawberry Nauts | PSV | 2016 | Adventure | 0.00 | 0.0 | 0.01 | 0.0 | NaN | NaN | RP | 0.01 |
| 16710 | Samurai Warriors: Sanada Maru | PS3 | 2016 | Action | 0.00 | 0.0 | 0.01 | 0.0 | NaN | NaN | RP | 0.01 |
| 16712 | Haitaka no Psychedelica | PSV | 2016 | Adventure | 0.00 | 0.0 | 0.01 | 0.0 | NaN | NaN | RP | 0.01 |
| 16714 | Winning Post 8 2016 | PSV | 2016 | Simulation | 0.00 | 0.0 | 0.01 | 0.0 | NaN | NaN | RP | 0.01 |
1105 rows × 12 columns
# динамика продаж актуальных платформ
actual_sales = new_df.pivot_table(index='year_of_release',
columns='platform',
values='total_sales',
aggfunc='sum',
fill_value=0
)
actual_sales
| platform | 3DS | PC | PS3 | PS4 | PSV | Wii | WiiU | X360 | XOne |
|---|---|---|---|---|---|---|---|---|---|
| year_of_release | |||||||||
| 2015 | 21.30 | 8.52 | 16.82 | 79.50 | 6.25 | 1.14 | 14.89 | 11.96 | 50.64 |
| 2016 | 8.57 | 5.25 | 3.60 | 54.18 | 4.25 | 0.18 | 4.60 | 1.52 | 24.10 |
Среди существующих платформ выделим топ-5, а также наиболее перспективные платформы.
# топ-5 существующих платформ по общим продажам
top5_platforms = (actual_sales.sum()
.sort_values(ascending=False)
.head(5)
.copy()
.reset_index()
)
# добавим в датафрейм индекс с названиями платформ
top5_platforms.set_index(keys=top5_platforms['platform'], inplace=True)
top5_platforms_list = list(top5_platforms['platform'])
print('Список топ-5 существующих платформ по общим продажам в 2015-2016 гг.:', top5_platforms_list)
Список топ-5 существующих платформ по общим продажам в 2015-2016 гг.: ['PS4', 'XOne', '3DS', 'PS3', 'WiiU']
fig = px.bar(top5_platforms,
color='platform',
title='Топ-5 существующих платформ <br>по общим продажам в 2015-2016 гг.')
fig.update_xaxes(title_text='Объем продаж, млн. долл.')
fig.update_yaxes(title_text='Игровая платформа')
fig.show()
# сводная таблица по продажам 2014-2016 гг.
sales_14_16 = (
df.query('platform in @new_platforms & year_of_release >= 2012')
.pivot_table(index='year_of_release',
columns='platform',
values='total_sales',
aggfunc='sum',
fill_value=0)
)
# проверка раста продаж для платформ
ascend_query = list(sales_14_16.loc[2012] < sales_14_16.loc[2016])
# перспективные платформы
prospect_platforms = \
new_df.pivot_table(index='platform',
columns='year_of_release',
values='total_sales',
aggfunc='sum', fill_value=0
)[ascend_query].index
print('Перспективные платформы:', list(prospect_platforms))
Перспективные платформы: ['PS4', 'XOne']
Уточним список перспективных платформ с помощью критериев, которые были указаны чуть раньше.
Таким образом к перспективным платформам можно отнести только «PS4» и «XOne» и исключить остальные из сравнения.
# датасет для отобранных перспективных платформ в актуальный период
prospect_platforms_df = new_df.query('platform in @prospect_platforms')
prospect_platforms_df
| name | platform | year_of_release | genre | na_sales | eu_sales | jp_sales | other_sales | critic_score | user_score | rating | total_sales | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 31 | Call of Duty: Black Ops 3 | PS4 | 2015 | Shooter | NaN | NaN | 0.36 | NaN | NaN | NaN | RP | 0.36 |
| 77 | FIFA 16 | PS4 | 2015 | Sports | 1.12 | NaN | 0.06 | NaN | 82.0 | 4.3 | E | 1.18 |
| 87 | Star Wars Battlefront (2015) | PS4 | 2015 | Shooter | NaN | NaN | 0.22 | NaN | NaN | NaN | RP | 0.22 |
| 94 | FIFA 17 | PS4 | 2016 | Sports | 0.66 | NaN | 0.08 | NaN | 85.0 | 5.0 | E | 0.74 |
| 99 | Call of Duty: Black Ops 3 | XOne | 2015 | Shooter | NaN | NaN | 0.01 | NaN | NaN | NaN | RP | 0.01 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 16630 | Sébastien Loeb Rally Evo | XOne | 2016 | Racing | 0.00 | 0.01 | 0.00 | 0.0 | 63.0 | 8.2 | E | 0.01 |
| 16643 | Rugby Challenge 3 | XOne | 2016 | Sports | 0.00 | 0.01 | 0.00 | 0.0 | NaN | 6.6 | E | 0.01 |
| 16645 | ZombiU | XOne | 2016 | Action | 0.00 | 0.01 | 0.00 | 0.0 | NaN | NaN | RP | 0.01 |
| 16660 | Prison Architect | XOne | 2016 | Action | 0.01 | 0.00 | 0.00 | 0.0 | 74.0 | 6.7 | RP | 0.01 |
| 16672 | Metal Gear Solid V: The Definitive Experience | XOne | 2016 | Action | 0.01 | 0.00 | 0.00 | 0.0 | NaN | NaN | M | 0.01 |
468 rows × 12 columns
# график распределения продаж отдельных игр для перспективных платформ
fig = px.violin(prospect_platforms_df
.pivot_table(index=prospect_platforms_df.index,
columns='platform',
values='total_sales',
aggfunc='sum'),
color='platform',
box=True,
points='all',
title='График распределения продаж отдельных игр <br>для перспективных платформ')
fig.update(layout_yaxis_range = [0,1])
fig.update_xaxes(title_text='Игровая платформа')
fig.update_yaxes(title_text='Объем продаж, млн. долл.')
fig.show()
В топ-5 существующих платформ уже определились лидеры, так что графики строим только для платформ «PS4» и «XOne».
Медианные значения обеих платформ находятся приблизительно на одном уровне - у «XOne» он чуть более высокий. Также платформа «XOne» имеет больший межквартильного размах, а «PS4» большую концентрацию значений вокруг медианы.
# оценка количества отсутствующих значений от общего размера датасета
prospect_platforms_df.info()
<class 'pandas.core.frame.DataFrame'> Int64Index: 468 entries, 31 to 16672 Data columns (total 12 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 name 468 non-null object 1 platform 468 non-null object 2 year_of_release 468 non-null int64 3 genre 468 non-null object 4 na_sales 465 non-null float64 5 eu_sales 455 non-null float64 6 jp_sales 468 non-null float64 7 other_sales 458 non-null float64 8 critic_score 297 non-null float64 9 user_score 311 non-null float64 10 rating 468 non-null object 11 total_sales 468 non-null float64 dtypes: float64(7), int64(1), object(4) memory usage: 47.5+ KB
# датасет для перспективных платформ с удаленными NaN
prospect_platforms_df_cutted = prospect_platforms_df.dropna(subset=['critic_score', 'user_score']).copy()
prospect_platforms_df_cutted
| name | platform | year_of_release | genre | na_sales | eu_sales | jp_sales | other_sales | critic_score | user_score | rating | total_sales | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 77 | FIFA 16 | PS4 | 2015 | Sports | 1.12 | NaN | 0.06 | NaN | 82.0 | 4.3 | E | 1.18 |
| 94 | FIFA 17 | PS4 | 2016 | Sports | 0.66 | NaN | 0.08 | NaN | 85.0 | 5.0 | E | 0.74 |
| 105 | Fallout 4 | PS4 | 2015 | Role-Playing | 2.53 | NaN | 0.24 | NaN | 87.0 | 6.5 | M | 2.77 |
| 171 | Uncharted 4: A Thief's End | PS4 | 2016 | Shooter | 1.85 | NaN | 0.19 | NaN | 93.0 | 7.9 | T | 2.04 |
| 231 | Uncharted: The Nathan Drake Collection | PS4 | 2015 | Action | 2.07 | 1.71 | 0.08 | NaN | 86.0 | 8.1 | T | 3.86 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 16526 | Dungeons 2 | PS4 | 2016 | Role-Playing | 0.01 | 0.00 | 0.00 | 0.0 | 61.0 | 7.9 | T | 0.01 |
| 16530 | Carmageddon: Max Damage | PS4 | 2016 | Action | 0.01 | 0.00 | 0.00 | 0.0 | 51.0 | 5.5 | M | 0.01 |
| 16597 | Saints Row: Gat out of Hell | XOne | 2015 | Action | 0.00 | 0.01 | 0.00 | 0.0 | 65.0 | 6.0 | M | 0.01 |
| 16630 | Sébastien Loeb Rally Evo | XOne | 2016 | Racing | 0.00 | 0.01 | 0.00 | 0.0 | 63.0 | 8.2 | E | 0.01 |
| 16660 | Prison Architect | XOne | 2016 | Action | 0.01 | 0.00 | 0.00 | 0.0 | 74.0 | 6.7 | RP | 0.01 |
290 rows × 12 columns
Для проведения анализа связей между продажами игр и их рейтингами, имеющиеся строки с NaN были удалены. Размер датасета уменьшился на треть, но это позволяет избежать искажений от усиления средних рейтингов.
Ранее мы посчитали платформу «PS4» более предпочтительной, потому анализ зависимости между рейтингами игр и объемами их продаж будем проводить для нее.
# срез для платформы PS4
query_ps4 = prospect_platforms_df_cutted.query('platform =="PS4"')
# расчет коэффициентов корреляции для платформы PS4
critic_score_corr = query_ps4['critic_score'].corr(query_ps4['total_sales'])
user_score_corr = query_ps4['user_score'].corr(query_ps4['total_sales'])
print('Корреляция между рейтингом критиков и продажами:', critic_score_corr,
'\nКорреляция между рейтингом пользователей и продажами', user_score_corr)
Корреляция между рейтингом критиков и продажами: 0.4249884643634065 Корреляция между рейтингом пользователей и продажами 0.02917016772517034
# диаграмма рассеяния оценок критиков для платформы PS4
fig = px.scatter(query_ps4,
x='user_score',
y='total_sales',
title='Диаграмма зависимости между оценками критиков <br>и продажами для платформы «PS4»')
fig.update_xaxes(title_text='Оценки критиков, ед.')
fig.update_yaxes(title_text='Объем продаж, млн. долл.')
fig.show()
# диаграмма рассеяния оценок пользователей для платформы PS4
fig = px.scatter(query_ps4,
x='user_score',
y='total_sales',
title='Диаграмма зависимости между оценками пользователей <br>и продажами для платформы «PS4»')
fig.update_xaxes(title_text='Оценки пользователей, ед.')
fig.update_yaxes(title_text='Объем продаж, млн. долл.')
fig.show()
# корреляция между рейтингами
fig = px.imshow(prospect_platforms_df_cutted.query('platform =="PS4"')[['critic_score', 'user_score', 'total_sales']]
.corr().round(2),
title='Корреляция между рейтингами <br>и продажами для платформы «PS4»',
text_auto=True)
fig.update_xaxes(side="top")
fig.show()
Мы уже сделали выбор в пользу «PS4» и увидели как связаны отзывы с объемом продаж. Посмотрим, можно полученные для «PS4» параметры корреляции соотнести с другими платформами. А точнее, со второй перспективной платформой «XOne».
# корреляция рейтингов и продаж для платформы XOne
fig = px.imshow(prospect_platforms_df_cutted.query('platform =="XOne"')[['critic_score', 'user_score', 'total_sales']]
.corr().round(2),
title='Корреляция между рейтингами <br>и продажами для платформы «XOne»',
text_auto=True)
fig.update_xaxes(side="top")
fig.show()
# корреляция рейтингов и продаж для всех актуальных платформ
(
df.query('platform in @top5_platforms.index')
.dropna(subset=['critic_score', 'user_score', 'total_sales'])
.groupby('platform')[['critic_score', 'user_score', 'total_sales']]
.corr()
)
| critic_score | user_score | total_sales | ||
|---|---|---|---|---|
| platform | ||||
| 3DS | critic_score | 1.000000 | 0.741440 | 0.367392 |
| user_score | 0.741440 | 1.000000 | 0.276057 | |
| total_sales | 0.367392 | 0.276057 | 1.000000 | |
| PS3 | critic_score | 1.000000 | 0.631154 | 0.516953 |
| user_score | 0.631154 | 1.000000 | 0.308589 | |
| total_sales | 0.516953 | 0.308589 | 1.000000 | |
| PS4 | critic_score | 1.000000 | 0.557654 | 0.409130 |
| user_score | 0.557654 | 1.000000 | 0.013981 | |
| total_sales | 0.409130 | 0.013981 | 1.000000 | |
| WiiU | critic_score | 1.000000 | 0.770080 | 0.342312 |
| user_score | 0.770080 | 1.000000 | 0.362236 | |
| total_sales | 0.342312 | 0.362236 | 1.000000 | |
| XOne | critic_score | 1.000000 | 0.472462 | 0.386290 |
| user_score | 0.472462 | 1.000000 | -0.092953 | |
| total_sales | 0.386290 | -0.092953 | 1.000000 |
Анализируются данные за выделенный актуальный период в существовании платформы. На диаграммах рассеяния можно заметить, что некоторые игры - лидеры продаж, получившие высокую оценку критиков, у пользователей получают существенно более низкую оценку. И такое смещение - это общая тенденция. Связано это с тем, что пользователи вносят в оценку много субъективных факторов. Часто оценка выставляется, только чтобы излить негативные эмоции.
Бросается в глаза, что очень мало игр с низким, < 60% рейтингом. Такие, видимо, вообще нет смысла продавать.
В целом, рейтинг пользователей не влияет на объем продаж игры, а коэффициент корреляции близкий нулю (-0.03) это подтверждает.
Для рейтинга критиков коэффициент корреляции 0.4 показывает среднюю положительную связь с продажами. Высокий рейтинг критиков способствует увеличению продаж, но сам по себе их не гарантирует.
Выводы по исследованию связи рейтинга и продаж для платформы «PS4» можно распространить и на другие платформы, поскольку рейтинг игры учитывает только особенности самой игры - он платформо-независим, а сами выводы были ожидаемы.
# актуальный период
actual_period = [2015, 2016]
# распределение игр по жанрам за 2015-2016 гг.
genres_total_actual = new_df.query('year_of_release in @actual_period').groupby('genre')['total_sales'].sum()
# диаграмма распределение игр по жанрам
fig = go.Figure(data=[go.Pie(labels=genres_total_actual.index, values=genres_total_actual.values, hole=.3)])
fig.update_layout(title='Распределение игр по жанрам <br>в общем объеме продаж для всех платформ за 2015-2016 гг.')
fig.show()
# сводная таблица по жанрам за 2015-2016 гг. для действующих платформ
(
new_df.query('year_of_release in @actual_period')
.groupby('genre')['total_sales']
.agg(['sum', 'median', 'count'])
.sort_values(by='sum', ascending=False)
)
| sum | median | count | |
|---|---|---|---|
| genre | |||
| Action | 92.85 | 0.060 | 428 |
| Shooter | 66.65 | 0.360 | 81 |
| Sports | 45.21 | 0.120 | 107 |
| Role-Playing | 44.05 | 0.115 | 132 |
| Misc | 14.17 | 0.060 | 71 |
| Fighting | 12.37 | 0.090 | 37 |
| Adventure | 11.98 | 0.030 | 110 |
| Racing | 10.86 | 0.055 | 42 |
| Platform | 9.28 | 0.100 | 28 |
| Simulation | 6.15 | 0.100 | 33 |
| Strategy | 2.98 | 0.050 | 29 |
| Puzzle | 0.72 | 0.030 | 7 |
# выборка по годам для всех платформ в 2016-ом году
genres_total = new_df.query('year_of_release in @actual_period and platform == "PS4"').groupby('genre')['total_sales'].sum()
# диаграмма распределение игр по жанрам в общем объеме продаж в 2016-ом году
fig = go.Figure(data=[go.Pie(labels=genres_total.index, values=genres_total.values, hole=.3)])
fig.update_layout(title='Распределение игр по жанрам <br>в общем объеме продаж <br>для платформы «PS4» за 2015-2016 гг.')
fig.show()
# сводная таблица по жанрам за 2015-2016 гг. для действующих платформ
(
new_df.query('year_of_release in @actual_period and platform == "PS4"')
.groupby('genre')['total_sales']
.agg(['sum', 'median', 'count'])
.sort_values(by='sum', ascending=False)
)
| sum | median | count | |
|---|---|---|---|
| genre | |||
| Action | 45.30 | 0.100 | 111 |
| Shooter | 26.92 | 0.360 | 31 |
| Sports | 19.72 | 0.300 | 31 |
| Role-Playing | 16.98 | 0.120 | 43 |
| Fighting | 7.57 | 0.165 | 16 |
| Racing | 5.76 | 0.130 | 14 |
| Adventure | 5.16 | 0.065 | 24 |
| Platform | 2.49 | 0.140 | 7 |
| Misc | 2.23 | 0.045 | 14 |
| Simulation | 1.23 | 0.210 | 6 |
| Strategy | 0.30 | 0.080 | 3 |
| Puzzle | 0.02 | 0.020 | 1 |
Для начала оценим какую долю рынка занимали отдельные жанровые категории за период с 2015 по 2016 годы. Здесь явно выделяются четыре жанра - Action, Shooter, Sports, Role-Playing.
Мы знаем, в этот период с серьезным отрывом лидировала платформа «PS4». Посмотрим какую долю рынка формировали продажи именно этой платформы.
# расчет суммарных продаж по платформам
sales2016 = new_df.query('year_of_release == 2016').groupby('platform')['total_sales'].sum()
sales2016
platform 3DS 8.57 PC 5.25 PS3 3.60 PS4 54.18 PSV 4.25 Wii 0.18 WiiU 4.60 X360 1.52 XOne 24.10 Name: total_sales, dtype: float64
# доля рынка PS4
print('Доля продаж игр на платформе "PS4" в суммарных продажах для всех платформ в 2016 году: {:.1%}'
.format(sales2016[3] / sales2016.sum()))
Доля продаж игр на платформе "PS4" в суммарных продажах для всех платформ в 2016 году: 51.0%
Так как доля платформы «PS4» на рынке составляет более половины, то стоит предположить, что именно она и формирует основные тенденции. Поэтому стоит взглянуть на динамику продаж топовых жанров только для этой платформы.
# основные игровые жанры
top_genres = ['Shooter', 'Action', 'Sports']
# диаграмма изменения продаж топовых жанров
fig = px.line(df.query('platform == "PS4" and year_of_release >= 2012 and genre in @top_genres')
.pivot_table(index='year_of_release', columns='genre', values='total_sales', aggfunc='sum', fill_value=0),
title='Динамика продаж основных игровых жанров <br>для платформы «PS4»')
fig.update_xaxes(title_text='Год выпуска',
tickvals = [2012, 2013, 2014, 2015, 2016])
fig.update_yaxes(title_text='Объем продаж, млн. долл.')
fig.show()
Видно, что жанр Action сдал свои позиции к 2016 году. Теперь лидером является Shooter и продажи этого жанра растут. Однако, его лидерство никак не отображается на картине продаж в целом по платформам за период с 2015 по 2016 гг. - он все же немного уступает жанру Action.
Из этого можно заключить, что для дальнейшего анализа стоит использовать самые свежие данные за 2016 год. Посмотрим, как распределились объемы продаж отдельных жанров.
# выборка по годам для всех платформ в 2016-ом году
genres_total = new_df.query('year_of_release == 2016').groupby('genre')['total_sales'].sum()
# диаграмма распределение игр по жанрам в общем объеме продаж в 2016-ом году
fig = go.Figure(data=[go.Pie(labels=genres_total.index, values=genres_total.values, hole=.3)])
fig.update_layout(title='Распределение игр по жанрам <br>в общем объеме продаж <br>для всех платформ в 2016 году')
fig.show()
# сводная таблица по жанрам за 2016 г. для действующих платформ
(
new_df.query('year_of_release == 2016')
.groupby('genre')['total_sales']
.agg(['sum', 'median', 'count'])
.sort_values(by='sum', ascending=False)
)
| sum | median | count | |
|---|---|---|---|
| genre | |||
| Shooter | 30.01 | 0.240 | 47 |
| Action | 28.78 | 0.050 | 178 |
| Sports | 14.58 | 0.120 | 48 |
| Role-Playing | 12.94 | 0.120 | 54 |
| Fighting | 4.47 | 0.085 | 16 |
| Adventure | 3.82 | 0.020 | 56 |
| Platform | 3.23 | 0.100 | 15 |
| Racing | 2.79 | 0.050 | 24 |
| Misc | 2.60 | 0.040 | 32 |
| Simulation | 1.89 | 0.025 | 18 |
| Strategy | 1.13 | 0.060 | 13 |
| Puzzle | 0.01 | 0.010 | 1 |
На оценку популярности игровых жанров влияют не только текущие предпочтения пользователей, по точность классификации игр по жанрам, даже сами жанры могут со временем меняться или исчезать. Поэтому будем рассматривать самую актуальную информацию.
На диаграмме распределение игр по жанрам в общем объеме продаж в 2016 году видно на рынке компьютерных игр преобладает жанр Shooter, также велики доли игр жанров Action, Sports и Role-Playing.
Такая картина обусловлена технической мощью современных игровых платформ, а значит возможностью предложить разработчиками еще более захватывающие, насыщенные действием и качественной графикой играми. Это является надежной гарантией удержания пользовательского интереса к играм типа экшен и в будущем.
Хотелось бы обратить внимание на крайне низкую долю жанра Strategy. Игры этого жанра, предназначенные для персональных компьютеров весьма популярны, имеют широкий круг фанатов, вокруг них собственная субкультура (например, "Warcraft", переросший из стратегии реального времени в ролевой "World of Warcraft"). "Неприсобленность" стратегий для игровых приставок и их эволюция в более активные жанры лишний раз показывает специфическую нацеленность приставок на экшен-игры в широком смысле этого слова.
# выборка по годам для для платформы PS4 в 2016-ом году
genres_total = new_df.query('year_of_release == 2016 and platform == "PS4"').groupby('genre')['total_sales'].sum()
# диаграмма распределение игр по жанрам в общем объеме продаж в 2016-ом году
fig = go.Figure(data=[go.Pie(labels=genres_total.index, values=genres_total.values, hole=.3)])
fig.update_layout(title='Распределение игр по жанрам <br>в общем объеме продаж <br>для платформы «PS4» в 2016 году')
fig.show()
# сводная таблица по жанрам за 2016 г. для платформы PS4
(
new_df.query('year_of_release == 2016 and platform == "PS4"')
.groupby('genre')['total_sales']
.agg(['sum', 'median', 'count'])
.sort_values(by='sum', ascending=False)
)
| sum | median | count | |
|---|---|---|---|
| genre | |||
| Shooter | 15.98 | 0.305 | 20 |
| Action | 14.85 | 0.060 | 59 |
| Sports | 7.72 | 0.315 | 16 |
| Role-Playing | 5.98 | 0.200 | 18 |
| Fighting | 2.74 | 0.150 | 7 |
| Adventure | 2.19 | 0.030 | 14 |
| Platform | 2.16 | 0.140 | 5 |
| Racing | 1.19 | 0.070 | 9 |
| Misc | 0.60 | 0.035 | 10 |
| Simulation | 0.55 | 0.060 | 4 |
| Strategy | 0.22 | 0.110 | 2 |
К 2016 году платформа «PS4» заняла больше половины рынка, так что распределение жанров для нее само по себе показательно, кроме того она была определена ранее как наиболее перспективная.
Диаграмма для «PS4» почти не отличается от общей, что не удивительно. За несколько лет список наиболее популярных жанров не изменился. В топе - Action, Shooter, Sports, Role-Playing. Основное изменение в том, что жанр Shooter усилил свою позиции и вытеснил жанр Action с верхней строчки топа.
Обращает на себя внимание и то, что типичное медианное значения объема продаж игр жанра Shooter почти в 5 раз превышает значение для Action, который удерживает позиции только благодаря большому разнообразию игр. Но к 2016 году заметно как падение количества таких игр, так и их цена.
В ходе анализа была исследована динамика объема продаж игр для различных платформ. Были определены характерный период жизни игровой платформы - 8 лет, период роста продаж - 4 года и актуальный период - 2 года. На основе сравнения актуальных платформ была выделена наиболее перспективная платформа - «PS4».
Также было проведено сравнение различных игровых жанров, исследованы влияющие на прибыльность факторы, и выбраны наиболее популярные - Shooter, Action, Sports, Role-Playing. При этом жанр Shooter кажется наиболее предпочтительным.
Составить портрет пользователя каждого региона.
Определить для пользователя каждого региона (NA, EU, JP):
Для создания портрета типичного пользователя нам стоит захватить достаточно широкий круг данных. В качестве временного периода выберем актуальный период с 2015 по 2016 год.
# расчет суммарных продаж отдельных платформ по регионам
platform_region = (
new_df.query('year_of_release in @actual_period').loc[:, ['platform', 'na_sales', 'eu_sales', 'jp_sales']]
.groupby('platform').sum()
)
platform_region
| na_sales | eu_sales | jp_sales | |
|---|---|---|---|
| platform | |||
| 3DS | 7.33 | 7.39 | 13.48 |
| PC | 3.72 | 9.10 | 0.00 |
| PS3 | 5.66 | 7.30 | 4.97 |
| PS4 | 54.84 | 48.28 | 12.05 |
| PSV | 0.54 | 1.08 | 8.41 |
| Wii | 0.49 | 0.73 | 0.00 |
| WiiU | 8.86 | 6.49 | 2.66 |
| X360 | 8.05 | 4.21 | 0.00 |
| XOne | 45.71 | 21.82 | 0.18 |
# график объема продаж по платформам в зависимости от региона
fig = px.bar(platform_region,
barmode='group',
title='Объем продаж по игровым платформам <br>в зависимости от региона')
fig.update_xaxes(title_text='Платформы')
fig.update_yaxes(title_text='Объем продаж, млн. долл.')
fig.show()
# топ-5 платформ по продажам в Северной Америке
platform_region['na_sales'].sort_values(ascending=False).head()
platform PS4 54.84 XOne 45.71 WiiU 8.86 X360 8.05 3DS 7.33 Name: na_sales, dtype: float64
# топ-5 платформ по продажам в Европе
platform_region['eu_sales'].sort_values(ascending=False).head()
platform PS4 48.28 XOne 21.82 PC 9.10 3DS 7.39 PS3 7.30 Name: eu_sales, dtype: float64
# Топ-5 платформ по продажам в Японии
platform_region['jp_sales'].sort_values(ascending=False).head()
platform 3DS 13.48 PS4 12.05 PSV 8.41 PS3 4.97 WiiU 2.66 Name: jp_sales, dtype: float64
В Северной Америке и Европе в топ-5 вошли почти одни и те же платформы, вверху списка расположились «PS4»(PlayStation 4) и «XOne»(Xbox One). В Японии вверху топ-5 расположилась «3DS»(Nintendo 3DS), а «XOne» даже не попала в пятерку. На примере «XOne»(Xbox One) американской компании "Microsoft" видно, что региональные потребители отдают предпочтения местным компаниям - в Северной Америке «XOne» продается больше, чем где-либо еще. Европа следует в предпочтениях за США, вероятно из-за отсутствия игровых приставок собственного производства. Не вызывает удивления, что лидирующую позицию в целом заняла самая современная (на 2016 год) приставка «PS4»(PlayStation 4) от японской транснациональной корпорации "Sony", при этом с сильным отрывом от конкурентов. В Японии весь топ-5 заняли приставки японский компаний и вверху оказалась платформа «3DS»(Nintendo 3DS) от национальной японской компании «Nintendo».
# расчет суммарных продаж отдельных жанров по регионам
genre_region = (
new_df.query('year_of_release in @actual_period').loc[:, ['genre', 'na_sales', 'eu_sales', 'jp_sales']]
.groupby('genre').sum()
)
genre_region
| na_sales | eu_sales | jp_sales | |
|---|---|---|---|
| genre | |||
| Action | 33.68 | 32.83 | 16.52 |
| Adventure | 3.84 | 4.67 | 2.22 |
| Fighting | 5.54 | 3.72 | 1.54 |
| Misc | 5.58 | 4.26 | 3.19 |
| Platform | 3.78 | 3.20 | 1.42 |
| Puzzle | 0.06 | 0.13 | 0.52 |
| Racing | 2.82 | 6.52 | 0.29 |
| Role-Playing | 16.84 | 11.81 | 11.15 |
| Shooter | 34.57 | 22.17 | 2.33 |
| Simulation | 1.36 | 3.74 | 0.53 |
| Sports | 26.31 | 11.78 | 1.65 |
| Strategy | 0.82 | 1.57 | 0.39 |
# график объема продаж по жанрам в зависимости от региона
fig = px.bar(genre_region,
barmode='group',
title='Объем продаж по игровым жанрам <br>в зависимости от региона')
fig.update_xaxes(title_text='Жанры')
fig.update_yaxes(title_text='Объем продаж, млн. долл.')
fig.show()
# топ-5 жанров по продажам в Северной Америке
genre_region['na_sales'].sort_values(ascending=False).head()
genre Shooter 34.57 Action 33.68 Sports 26.31 Role-Playing 16.84 Misc 5.58 Name: na_sales, dtype: float64
# топ-5 жанров по продажам в Европе
genre_region['eu_sales'].sort_values(ascending=False).head()
genre Action 32.83 Shooter 22.17 Role-Playing 11.81 Sports 11.78 Racing 6.52 Name: eu_sales, dtype: float64
# топ-5 жанров по продажам в Японии
genre_region['jp_sales'].sort_values(ascending=False).head()
genre Action 16.52 Role-Playing 11.15 Misc 3.19 Shooter 2.33 Adventure 2.22 Name: jp_sales, dtype: float64
Сразу бросается в глаза отличие японского топ-5 от двух других. В Японии такой популярный жанр как Shooter неожиданно оказался не вверху, внизу топ-5, его вытеснил жанр Role-Playing. Также в этом топе вместо жанра Sports появился жанр Adventure. Вероятно, связано это с общей мифологизацией образа мышления у японцев и желанием сбежать в фантазийный виртуальный мир от давления социума. Вообще, почти маниакальное увлечение молодыми японцами такого рода компьютерными играми хорошо видно по тому, какое количество японских аниме посвящено перерождению героя в фантазийном или компьютерном мире.
Для Северной Америки и Европы топ-5 отличается незначительно. Можно обратить внимание на жанр Sports, который в Северной Америки расположился строчкой ниже чем в Европе. Напрашивается очевидное объяснение - значительную часть этой категории занимают футбольные игры, а в США футбол не популярен. А жанр Shooter в Европе пока еще не достиг такой популярности, как в Северной Америке.
# расчет суммарных продаж по категориям ESRB по регионам
rating_region = (
new_df.query('year_of_release in @actual_period').loc[:, ['rating', 'na_sales', 'eu_sales', 'jp_sales']].groupby('rating').sum()
)
rating_region
| na_sales | eu_sales | jp_sales | |
|---|---|---|---|
| rating | |||
| E | 26.59 | 18.79 | 3.85 |
| E10+ | 17.87 | 11.54 | 1.81 |
| M | 47.03 | 34.00 | 4.29 |
| RP | 20.85 | 24.25 | 24.10 |
| T | 22.86 | 17.82 | 7.70 |
# график объема продаж по категориям рейтинга ESRB в зависимости от региона
fig = px.bar(rating_region,
barmode='group',
title='Объем продаж по категориям рейтинга ESRB <br>в зависимости от региона')
fig.update_xaxes(title_text='Категории ESRB')
fig.update_yaxes(title_text='Объем продаж, млн. долл.')
fig.show()
# топ-5 категорий ESRB по продажам в Северной Америке
rating_region['na_sales'].sort_values(ascending=False).head()
rating M 47.03 E 26.59 T 22.86 RP 20.85 E10+ 17.87 Name: na_sales, dtype: float64
# топ-5 категорий ESRB по продажам в Европе
rating_region['eu_sales'].sort_values(ascending=False).head()
rating M 34.00 RP 24.25 E 18.79 T 17.82 E10+ 11.54 Name: eu_sales, dtype: float64
# топ-5 категорий ESRB по продажам в Японии
rating_region['jp_sales'].sort_values(ascending=False).head()
rating RP 24.10 T 7.70 M 4.29 E 3.85 E10+ 1.81 Name: jp_sales, dtype: float64
В топ-5 во всех регионах вошли одни те же категории, описывающие универсального потребителя - взрослые и подростки, и не включающие в себя специфические категории «EC» («Для детей младшего возраста») или «AO» («Только для взрослых»). В Японии на первом месте оказалась «RP» («Рейтинг ожидается») — можно предположить, что за этим стоит категория «Для всех». Тогда некоторые отличия будут, ведь в Северной Америке и Европе первое место занимает категория «M»(«Для взрослых»). Но, скорее всего, — это не так.
Целью исследование было составление портрета типичного пользователя для нескольких регионов мира - Северной Америки, Европы и Японии. В ходе исследования игровых предпочтений пользователей различных регионов мира выяснилось, что они тяготеют к игровым платформам местного производства. Но, в целом, лидирующую позицию занимает «PS4»(PlayStation 4) компании "Sony", особенно в Европе.
Жанровые предпочтения отличаются, причем особенно сильно в Японии. В Европе и США предпочтения пользователей схожи, можно лишь заметить, что в Европе больше поклонников жанра Sports. В Японии и Европе топ возглавил достаточно универсальный жанр Action. В Северной Америке с первой строчки его потеснилShooter.
Изучение рейтинга ESRB ожидаемое показало, что основной целевой категорией пользователей являются взрослые. Заметим, однако, что категория рейтинга ESRB, присвоенная игре, больше говорит о содержании игры, а не от том, кто в нее играет.
Проверить гипотезы:
Action (англ. «действие», экшен-игры) и Sports (англ. «спортивные соревнования») разные.Проверка утверждения, что средние пользовательские рейтинги платформ «Xbox One» и «PC» одинаковые:
Для теста берутся полные выборки данных за период с 2008 по 2016 год. Пороговое значение alpha устанавливается в $5\%$ для двустороннего теста.
# датасет для всех платформ с удаленными NaN
user_scores = new_df.dropna(subset=['user_score']).copy()
# кол-во строк в срезе по платформам
user_scores.query('platform == "XOne"')['user_score'].count(), user_scores.query('platform == "PC"')['user_score'].count()
(121, 84)
# тест сравнения пользовательских рейтингов двух платформ по полным выборкам
xb1_sample = user_scores.query('platform == "XOne"')['user_score']
pc_sample = user_scores.query('platform == "PC"')['user_score']
alpha = 0.05
xb1_pc_result = st.ttest_ind(xb1_sample, pc_sample, equal_var=False)
print('p-value', xb1_pc_result.pvalue)
if xb1_pc_result.pvalue < alpha:
print('Отвергаем нулевую гипотезу.')
else:
print('Не получилось отвергнуть нулевую гипотезу.')
p-value 0.2946308864003345 Не получилось отвергнуть нулевую гипотезу.
Проверка утверждения, что пользовательские рейтинги жанров Action (англ. «действие») и Sports (англ. «виды спорта») разные
Action и Sports не больше и не меньше друг друга.Для теста берутся полные выборки данных за период с 2008 по 2016 год. Пороговое значение alpha устанавливается в $5\%$ для одностороннего теста.
# кол-во строк в срезе по жанрам
user_scores.query('genre == "Action"')['user_score'].count(), user_scores.query('genre == "Sports"')['user_score'].count()
(193, 82)
# тест сравнения пользовательских рейтингов двух жанров по полным выборкам
action_sample = user_scores.query('genre == "Action"')['user_score']
sports_sample = user_scores.query('genre == "Sports"')['user_score']
alpha = 0.05
action_sports_result = st.ttest_ind(action_sample, sports_sample, equal_var=False)
print('p-value', action_sports_result.pvalue)
if action_sports_result.pvalue / 2 < alpha:
print('Отвергаем нулевую гипотезу.')
else:
print('Не получилось отвергнуть нулевую гипотезу.')
p-value 5.97163549920592e-10 Отвергаем нулевую гипотезу.
В первом случае t-тест не смог опровергнуть гипотезу, что средние двух генеральных совокупностей пользовательских рейтингов платформ «Xbox One» и «PC» равны между собой. Во втором случае была принята альтернативная гипотеза о том, что средние двух генеральных совокупностей пользовательских рейтингов жанров Action и Sports отличаются.
Из результатов статистических тестов можно сделать вывод, что степень популярности игр среди пользователей не связана с популярностью платформы, но прямо зависит от ее жанра.
Исходные данные не нуждались в значительной предварительной обработке. Не было выявлено дубликатов и аномалий. Некоторым столбцам требовалось сменить тип данных, был изменен регистр названий столбцов. В столбцах с рейтингами был заменен ряд значений. Также там обнаружились многочисленные пропуски и большинство было оставлено. Исследовательский анализ данных в основном строится на изучении графиков и пропуски в виде NaN-значений не мешают их построению. Важнейшие изменения в данных были связаны с добавлением столбца общих продаж и обрезанием выбросов в столбцах продаж.
Исследование показало, что игры на платформе "PlayStation 4" от "Sony" являются наиболее перспективным выбором. Платформа к 2016 году занимает более половины всего рынка, а игровые хиты для нее показывают высокие объемы продаж.
При изучении динамики объема продаж на протяжении срока жизни игровой платформы был разработан критерий фиксация спада продаж, четко определена средняя длительность роста и выделен период актуальности платформы. Для игровой платформы "PlayStation 4" признаки начала снижения продаж обнаружены не были, более того, не достигнуто значение средней продолжительности периода роста. И это позволяет надеяться, что платформа будет популярна еще какое-то время.
Был определен портрет типичного пользователя. В качестве целевой возрастной были определены взрослые люди и подростки. Наиболее популярными жанрами являются шутеры, игры в стиле экшен и спортивные игры. Причем первые характеризуется еще и наиболее высоким типичным значением продаж.
Важным фактором успешной продажи конкретной игры является ее высокий рейтинг у профессиональных критиков - между ним и объемом продаж обнаружена прямая зависимость. Однако, высокий рейтинг высоких продаж сам по себе не гарантирует. На него влияют другие факторы. Не исключено, что лучше продаются продолжения уже хорошо зарекомендовавших себя игр.
Были проведены статистические тесты и проверены гипотезы о пользовательских предпочтениях в отношении жанров и игровых платформ. Ожидаемо выяснилось, что для пользователей скорее важен жанр игры, чем конкретная игровая платформа.